package com.example.chat.view.fab;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.ShapeDrawable.ShaderFactory;
import android.graphics.drawable.StateListDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;

import com.example.chat.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;


public class FloatingActionButton extends ImageButton {

        public static final int SIZE_NORMAL = 0;
        public static final int SIZE_MINI = 1;

        public int getMarginBottom() {
                int marginBottom = 0;
                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
                if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
                        marginBottom = ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin;
                }
                return marginBottom;
        }

        @Retention(RetentionPolicy.SOURCE)
        @IntDef({SIZE_NORMAL, SIZE_MINI})
        public @interface FAB_SIZE {
        }

        int mColorNormal;
        int mColorPressed;
        int mColorDisabled;
        String mTitle;
        @DrawableRes
        private int mIcon;
        private Drawable mIconDrawable;
        private int mSize;

        private float mCircleSize;
        private float mShadowRadius;
        private float mShadowOffset;
        private int mDrawableSize;
        boolean mStrokeVisible;

        public FloatingActionButton(Context context) {
                this(context, null);
        }

        public FloatingActionButton(Context context, AttributeSet attrs) {
                super(context, attrs);
                init(context, attrs);
        }

        public FloatingActionButton(Context context, AttributeSet attrs, int defStyle) {
                super(context, attrs, defStyle);
                init(context, attrs);
        }

        void init(Context context, AttributeSet attributeSet) {
                TypedArray attr = context.obtainStyledAttributes(attributeSet, R.styleable.FloatingActionButton, 0, 0);
                mColorNormal = attr.getColor(R.styleable.FloatingActionButton_fab_colorNormal, getColor(android.R.color.holo_blue_dark));
                mColorPressed = attr.getColor(R.styleable.FloatingActionButton_fab_colorPressed, getColor(android.R.color.holo_blue_light));
                mColorDisabled = attr.getColor(R.styleable.FloatingActionButton_fab_colorDisabled, getColor(android.R.color.darker_gray));
                mSize = attr.getInt(R.styleable.FloatingActionButton_fab_size, SIZE_NORMAL);
                mIcon = attr.getResourceId(R.styleable.FloatingActionButton_fab_icon, 0);
                mTitle = attr.getString(R.styleable.FloatingActionButton_fab_title);
                mStrokeVisible = attr.getBoolean(R.styleable.FloatingActionButton_fab_stroke_visible, true);
                attr.recycle();

                updateCircleSize();
                mShadowRadius = getDimension(R.dimen.fab_shadow_radius);
                mShadowOffset = getDimension(R.dimen.fab_shadow_offset);
                updateDrawableSize();

                updateBackground();
        }

        private void updateDrawableSize() {
                mDrawableSize = (int) (mCircleSize + 2 * mShadowRadius);
        }

        private void updateCircleSize() {
                mCircleSize = getDimension(mSize == SIZE_NORMAL ? R.dimen.fab_size_normal : R.dimen.fab_size_mini);
        }

        public void setSize(@FAB_SIZE int size) {
                if (size != SIZE_MINI && size != SIZE_NORMAL) {
                        throw new IllegalArgumentException("Use @FAB_SIZE constants only!");
                }

                if (mSize != size) {
                        mSize = size;
                        updateCircleSize();
                        updateDrawableSize();
                        updateBackground();
                }
        }

        @FAB_SIZE
        public int getSize() {
                return mSize;
        }

        public void setIcon(@DrawableRes int icon) {
                if (mIcon != icon) {
                        mIcon = icon;
                        mIconDrawable = null;
                        updateBackground();
                }
        }

        public void setIconDrawable(@NonNull Drawable iconDrawable) {
                if (mIconDrawable != iconDrawable) {
                        mIcon = 0;
                        mIconDrawable = iconDrawable;
                        updateBackground();
                }
        }

        /**
         * @return the current Color for normal state.
         */
        public int getColorNormal() {
                return mColorNormal;
        }

        public void setColorNormalResId(@ColorRes int colorNormal) {
                setColorNormal(getColor(colorNormal));
        }

        public void setColorNormal(int color) {
                if (mColorNormal != color) {
                        mColorNormal = color;
                        updateBackground();
                }
        }

        /**
         * @return the current color for pressed state.
         */
        public int getColorPressed() {
                return mColorPressed;
        }

        public void setColorPressedResId(@ColorRes int colorPressed) {
                setColorPressed(getColor(colorPressed));
        }

        public void setColorPressed(int color) {
                if (mColorPressed != color) {
                        mColorPressed = color;
                        updateBackground();
                }
        }

        /**
         * @return the current color for disabled state.
         */
        public int getColorDisabled() {
                return mColorDisabled;
        }

        public void setColorDisabledResId(@ColorRes int colorDisabled) {
                setColorDisabled(getColor(colorDisabled));
        }

        public void setColorDisabled(int color) {
                if (mColorDisabled != color) {
                        mColorDisabled = color;
                        updateBackground();
                }
        }

        public void setStrokeVisible(boolean visible) {
                if (mStrokeVisible != visible) {
                        mStrokeVisible = visible;
                        updateBackground();
                }
        }

        public boolean isStrokeVisible() {
                return mStrokeVisible;
        }

        int getColor(@ColorRes int id) {
                return getResources().getColor(id);
        }

        float getDimension(@DimenRes int id) {
                return getResources().getDimension(id);
        }

        public void setTitle(String title) {
                mTitle = title;
                TextView label = getLabelView();
                if (label != null) {
                        label.setText(title);
                }
        }

        TextView getLabelView() {
                return (TextView) getTag(R.id.fab_label);
        }

        public String getTitle() {
                return mTitle;
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                setMeasuredDimension(mDrawableSize, mDrawableSize);
        }

        void updateBackground() {
                final float strokeWidth = getDimension(R.dimen.fab_stroke_width);
                final float halfStrokeWidth = strokeWidth / 2f;

                LayerDrawable layerDrawable = new LayerDrawable(
                        new Drawable[]{
                                getResources().getDrawable(mSize == SIZE_NORMAL ? R.mipmap.fab_bg_normal : R.mipmap.fab_bg_mini),
                                createFillDrawable(strokeWidth),
                                createOuterStrokeDrawable(strokeWidth),
                                getIconDrawable()
                        });

                int iconOffset = (int) (mCircleSize - getDimension(R.dimen.fab_icon_size)) / 2;

                int circleInsetHorizontal = (int) (mShadowRadius);
                int circleInsetTop = (int) (mShadowRadius - mShadowOffset);
                int circleInsetBottom = (int) (mShadowRadius + mShadowOffset);

                layerDrawable.setLayerInset(1,
                        circleInsetHorizontal,
                        circleInsetTop,
                        circleInsetHorizontal,
                        circleInsetBottom);

                layerDrawable.setLayerInset(2,
                        (int) (circleInsetHorizontal - halfStrokeWidth),
                        (int) (circleInsetTop - halfStrokeWidth),
                        (int) (circleInsetHorizontal - halfStrokeWidth),
                        (int) (circleInsetBottom - halfStrokeWidth));

                layerDrawable.setLayerInset(3,
                        circleInsetHorizontal + iconOffset,
                        circleInsetTop + iconOffset,
                        circleInsetHorizontal + iconOffset,
                        circleInsetBottom + iconOffset);

                setBackgroundCompat(layerDrawable);
        }

        Drawable getIconDrawable() {
                if (mIconDrawable != null) {
                        return mIconDrawable;
                } else if (mIcon != 0) {
                        return getResources().getDrawable(mIcon);
                } else {
                        return new ColorDrawable(Color.TRANSPARENT);
                }
        }

        private StateListDrawable createFillDrawable(float strokeWidth) {
                StateListDrawable drawable = new StateListDrawable();
                drawable.addState(new int[]{-android.R.attr.state_enabled}, createCircleDrawable(mColorDisabled, strokeWidth));
                drawable.addState(new int[]{android.R.attr.state_pressed}, createCircleDrawable(mColorPressed, strokeWidth));
                drawable.addState(new int[]{}, createCircleDrawable(mColorNormal, strokeWidth));
                return drawable;
        }

        private Drawable createCircleDrawable(int color, float strokeWidth) {
                int alpha = Color.alpha(color);
                int opaqueColor = opaque(color);

                ShapeDrawable fillDrawable = new ShapeDrawable(new OvalShape());

                final Paint paint = fillDrawable.getPaint();
                paint.setAntiAlias(true);
                paint.setColor(opaqueColor);

                Drawable[] layers = {
                        fillDrawable,
                        createInnerStrokesDrawable(opaqueColor, strokeWidth)
                };

                LayerDrawable drawable = alpha == 255 || !mStrokeVisible
                        ? new LayerDrawable(layers)
                        : new TranslucentLayerDrawable(alpha, layers);

                int halfStrokeWidth = (int) (strokeWidth / 2f);
                drawable.setLayerInset(1, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth, halfStrokeWidth);

                return drawable;
        }

        private static class TranslucentLayerDrawable extends LayerDrawable {
                private final int mAlpha;

                public TranslucentLayerDrawable(int alpha, Drawable... layers) {
                        super(layers);
                        mAlpha = alpha;
                }

                @Override
                public void draw(Canvas canvas) {
                        Rect bounds = getBounds();
                        canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, mAlpha, Canvas.ALL_SAVE_FLAG);
                        super.draw(canvas);
                        canvas.restore();
                }
        }

        private Drawable createOuterStrokeDrawable(float strokeWidth) {
                ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());

                final Paint paint = shapeDrawable.getPaint();
                paint.setAntiAlias(true);
                paint.setStrokeWidth(strokeWidth);
                paint.setStyle(Style.STROKE);
                paint.setColor(Color.BLACK);
                paint.setAlpha(opacityToAlpha(0.02f));

                return shapeDrawable;
        }

        private int opacityToAlpha(float opacity) {
                return (int) (255f * opacity);
        }

        private int darkenColor(int argb) {
                return adjustColorBrightness(argb, 0.9f);
        }

        private int lightenColor(int argb) {
                return adjustColorBrightness(argb, 1.1f);
        }

        private int adjustColorBrightness(int argb, float factor) {
                float[] hsv = new float[3];
                Color.colorToHSV(argb, hsv);

                hsv[2] = Math.min(hsv[2] * factor, 1f);

                return Color.HSVToColor(Color.alpha(argb), hsv);
        }

        private int halfTransparent(int argb) {
                return Color.argb(
                        Color.alpha(argb) / 2,
                        Color.red(argb),
                        Color.green(argb),
                        Color.blue(argb)
                );
        }

        private int opaque(int argb) {
                return Color.rgb(
                        Color.red(argb),
                        Color.green(argb),
                        Color.blue(argb)
                );
        }

        private Drawable createInnerStrokesDrawable(final int color, float strokeWidth) {
                if (!mStrokeVisible) {
                        return new ColorDrawable(Color.TRANSPARENT);
                }

                ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());

                final int bottomStrokeColor = darkenColor(color);
                final int bottomStrokeColorHalfTransparent = halfTransparent(bottomStrokeColor);
                final int topStrokeColor = lightenColor(color);
                final int topStrokeColorHalfTransparent = halfTransparent(topStrokeColor);

                final Paint paint = shapeDrawable.getPaint();
                paint.setAntiAlias(true);
                paint.setStrokeWidth(strokeWidth);
                paint.setStyle(Style.STROKE);
                shapeDrawable.setShaderFactory(new ShaderFactory() {
                        @Override
                        public Shader resize(int width, int height) {
                                return new LinearGradient(width / 2, 0, width / 2, height,
                                        new int[]{topStrokeColor, topStrokeColorHalfTransparent, color, bottomStrokeColorHalfTransparent, bottomStrokeColor},
                                        new float[]{0f, 0.2f, 0.5f, 0.8f, 1f},
                                        TileMode.CLAMP
                                );
                        }
                });

                return shapeDrawable;
        }

        @SuppressWarnings("deprecation")
        @SuppressLint("NewApi")
        private void setBackgroundCompat(Drawable drawable) {
                if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
                        setBackground(drawable);
                } else {
                        setBackgroundDrawable(drawable);
                }
        }

        @Override
        public void setVisibility(int visibility) {
                TextView label = getLabelView();
                if (label != null) {
                        label.setVisibility(visibility);
                }

                super.setVisibility(visibility);
        }
}
